//
// Created by martin on 2/6/22.
//

#include "FileUtil.h"
#include "muduo/base/Logging.h"

#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

using namespace muduo;
using namespace muduo::FileUtil;

ReadSmallFile::ReadSmallFile(StringArg filename)
: fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)),
err_(0)
{
    buf_[0] = '\0';
    if (fd_ < 0)
    {
        err_ = errno;
    }
}

ReadSmallFile::~ReadSmallFile()
{
    if (fd_ > 0)
    {
        ::close(fd_); // FIXME: check EINTR
    }
}

/**
 * Read content from fd_
 * @tparam String std::string or can be cast to std::string
 * @param maxSize read max size from fd_ to content
 * @param content [out] store data read from fd_. Its size is minimum of file's real size and param size
 * @param size [out] store file's newest size
 * @param modifyTime [out] store file's newest modify time
 * @param createTime [out] store file's create time
 * @return errno
 */
template<class String>
int ReadSmallFile::readToString(int maxSize,
                                String *content,
                                int64_t *size,
                                int64_t *modifyTime,
                                int64_t *createTime)
{
    static_assert(sizeof(off_t) == 8, "_FILE_OFFSET_BITS = 64");
    assert(content != NULL);
    int err = err_;
    if (fd_ >= 0)
    {
        content->clear();

        if (size)
        {
            struct stat statbuf;
            if (::fstat(fd_, &statbuf) == 0)
            {
                if (S_ISREG(statbuf.st_mode))
                {
                    *size = statbuf.st_size;
                    content->reserve(static_cast<size_t>(std::min(implicit_cast<int64_t>(maxSize), *size)));
                }
                else if (S_ISDIR(statbuf.st_mode))
                {
                    err = EISDIR; // directory
                }

                if (modifyTime)
                {
                    *modifyTime = statbuf.st_mtime;
                }
                if (createTime)
                {
                    *createTime = statbuf.st_ctime;
                }
            }
            else
            {
                err = errno;
            }
        }

        while (content->size() < implicit_cast<size_t>(maxSize))
        {
            size_t toRead = std::min(implicit_cast<size_t>(maxSize) - content->size(), sizeof(buf_));
            ssize_t n = ::read(fd_, buf_, toRead);
            if (n > 0)
            {
                content->append(buf_, n);
            }
            else
            {
                if (n < 0)
                {
                    err = errno;
                }
                break;
            }
        }
    }

    return err;
}

int ReadSmallFile::readToBuffer(int *size)
{
    int err = err_;
    if (fd_ >= 0)
    {
        ssize_t n = ::pread(fd_, buf_, sizeof(buf_) - 1, 0);
        if (n >= 0)
        {
            if (size)
            {
                *size = static_cast<int>(n);
            }
            buf_[n] = '\0';
        }
        else
        {
            err = errno;
        }
    }
    return err;
}

AppendFile::AppendFile(StringArg filename)
: fp_(::fopen(filename.c_str(), "ae")), // 'e' for O_CLOEXEC
writtenBytes_(0)
{
    assert(fp_);
    ::setbuffer(fp_, buffer_, sizeof(buffer_)); // change stream fp_'s buffer to buffer_

#if 0
    // optimization for predeclaring an access pattern for file data
    struct stat statbuf;
    fstat(fd_, &statbuf);
    ::posix_fadvise(fp_, 0, statbuf.st_size, POSIX_FADV_DONTNEED);
#endif
}

AppendFile::~AppendFile()
{
    ::fclose(fp_);
}

/**
 * append string logline[0..len-1] to opened file fp_. file write operation will conducting.
 * @param logline char array logline[0..len-1]
 * @param len length of array logline
 */
void AppendFile::append(const char *logline, size_t len)
{
    size_t written = 0;

    /* write len byte to fp_ unless complete writing or error occurs */
    while (written != len)
    {
        size_t remain = len - written;
        size_t n = write(logline + written, remain);
        if (n != remain)
        {
            int err = ferror(fp_);
            if (err)
            {
                fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
                clearerr(fp_); // clear error indicators for fp_
                break;
            }
        }
        written += n;
    }

    writtenBytes_ += written;
}

void AppendFile::flush()
{
    ::fflush(fp_);
}

size_t AppendFile::write(const char *logline, size_t len)
{
    // not thread-safe
    return ::fwrite_unlocked(logline, 1, len, fp_);
}

template int FileUtil::readFile(
        StringArg filename,
        int maxSize,
        std::string* content,
        int64_t*, int64_t*, int64_t*);

template int FileUtil::ReadSmallFile::readToString(
        int maxSize,
        string* content,
        int64_t *, int64_t *, int64_t *);
